package com.supplychain.scm.core.task.strategy;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.fastjson.JSON;
import com.supplychain.scm.core.basis.constant.StringPool;
import com.supplychain.scm.core.basis.enums.ProductInfoEnum;
import com.supplychain.scm.core.task.AbstractTaskStrategy;
import com.supplychain.scm.core.task.bean.TaskAsyncParam;
import com.supplychain.scm.core.task.bean.TaskAsyncResultTO;
import com.supplychain.scm.core.task.bean.exporttask.ExcelHeader;
import com.supplychain.scm.core.task.bean.exporttask.TaskExport;
import com.supplychain.scm.core.task.bean.exporttask.TaskExportBO;
import com.supplychain.scm.core.task.constants.ExcelConstants;
import com.supplychain.scm.core.task.enums.TaskTypeEnum;
import com.supplychain.scm.core.task.util.EasyExcelUtil;
import com.supplychain.scm.core.basis.constant.CommonConstants;
import com.supplychain.scm.core.common.util.DateUtils;
import com.supplychain.scm.core.oss.model.DbyFile;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.springframework.util.Assert;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author liyang
 * @version 1.0.0
 * @ClassName AbstraceExportAsyncStrategy
 * @Description 导出异步任务抽象策略
 * @createTime 10:51:00 2023/04/14
 */
@Slf4j
public abstract class AbstractExportAsyncStrategy<Model> extends AbstractTaskStrategy<TaskAsyncParam, TaskAsyncResultTO> {

    @Override
    protected final TaskTypeEnum taskType() {
        return TaskTypeEnum.EXPORT_TASK;
    }

    @Override
    public final TaskAsyncResultTO taskAsyncHandler(TaskAsyncParam taskAsyncParam) throws Exception {
        TaskAsyncResultTO handler = null;
        try {
            taskAsyncParam = this.beforeHandler(taskAsyncParam);
            TaskExport taskExport = Optional.ofNullable(taskAsyncParam).map(TaskAsyncParam::getTaskParam).map(param -> JSON.parseObject(param, TaskExport.class)).orElse(TaskExport.builder().build());
            handler = this.exportExcel(taskExport);
        } catch (Exception e) {
            this.exceptionHandler(taskAsyncParam, handler);
            log.warn("策略处理器发生异常:{}", ExceptionUtils.getStackTrace(e));
            throw e;
        } finally {
            this.finallyHandler(taskAsyncParam, handler);
        }
        return handler;
    }

    /**
     * 异步任务导出分页查询
     *
     * @param searchParameter -> JOSN 查询条件
     * @param pageSize        页容量
     * @param pageNo          页码
     * @return
     */
    protected abstract List<Model> dataSourcePage(String searchParameter, Integer pageSize, Integer pageNo);

    /**
     * 数据总数
     *
     * @param searchParameter -> JSON 查询条件
     * @return
     */
    protected abstract long dataTotalCount(String searchParameter);

    private TaskAsyncResultTO exportExcel(TaskExport taskExport) throws IOException {
        long totalCount = this.dataTotalCount(taskExport.getSearchParameter());
        //是否压缩到ZIP包
        boolean isExportZip = totalCount > taskExport.getMaxExportCount();
        int index = CommonConstants.ONE;
        long count = isExportZip ? taskExport.getMaxExportCount() : totalCount;
        List<String> fileNames = Lists.newArrayList();
        do{
            long total = ((count * index) > totalCount) ? totalCount : (count * index);
            //总页数
            long totalPage = this.getTotalPage(total, taskExport.getPageSize());
            Long pageNum = isExportZip ? this.getTotalPage(count * (index - CommonConstants.ONE), taskExport.getPageSize()) + CommonConstants.ONE : CommonConstants.ONE;
            String fileName = ExcelConstants.DOWNLOAD_LOCAL_DEFAULT_DIR.concat(taskExport.getSheetName()).concat(StringPool.UNDERSCORE).concat(DateUtils.dateTimeNow()).concat(isExportZip ? StringPool.UNDERSCORE.concat(String.valueOf(index)) : StringPool.EMPTY).concat(ExcelConstants.DOT_FILE_TYPE_EXCEL);
            if (CollectionUtils.isEmpty(taskExport.getExportedFieldsParameter())) {
                taskExport.setExportedFieldsParameter(this.getExcelHeader());
            }
            ExcelWriter excelWriter = EasyExcel.write(fileName).head(this.headers(taskExport.getExportedFieldsParameter())).registerWriteHandler(cellStyle()).build();
            WriteSheet writeSheet = EasyExcel.writerSheet(taskExport.getSheetName()).build();
            for (int pageNo = pageNum.intValue(); pageNo <= totalPage; pageNo ++) {
                List<Model> models = this.dataSourcePage(taskExport.getSearchParameter(), taskExport.getPageSize(), pageNo);
                if(CollectionUtils.isNotEmpty(models)) {
                    List<List<Model>> allList = models.stream().map(model -> this.dataList(taskExport.getExportedFieldsParameter(), model))
                            .flatMap(Collection::stream).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(allList)) {
                        excelWriter.write(allList, writeSheet);
                    }
                }
            }
            excelWriter.finish();
            fileNames.add(fileName);
            index ++;
            if(isExportZip ? (total >= totalCount) : (total <= taskExport.getMaxExportCount())) {
                break;
            }
        } while (true);
        DbyFile dbyFile;
        if(isExportZip) {
            //压缩
            File zipFile = ZipUtil.zip(FileUtil.file(ExcelConstants.DOWNLOAD_LOCAL_DEFAULT_DIR.concat(taskExport.getSheetName()).concat(StringPool.UNDERSCORE).concat(DateUtils.dateTimeNow()).concat(ExcelConstants.DOT_FILE_TYPE_ZIP)),
                    false,
                    fileNames.stream().map(name -> FileUtil.file(name)).toArray(File[]::new));
            dbyFile = this.putFile(zipFile, taskExport.getProductInfoEnum());
            fileNames.forEach(item -> {
                File file = FileUtil.file(item);
                //删除本地文件
                if (file.exists()) {
                    FileUtil.del(file);
                }
            });
        } else {
            String fileName = fileNames.stream().findFirst().orElse(StringPool.EMPTY);
            dbyFile = this.putFile(fileName, taskExport.getProductInfoEnum());
        }
        return TaskAsyncResultTO.builder().taskResult(JSON.toJSONString(TaskExportBO.builder().exportFileUrl(dbyFile.getLink()).build())).build();
    }

    private DbyFile putFile(String fileName, ProductInfoEnum productInfoEnum) throws IOException {
        File file = FileUtil.file(fileName);
        return EasyExcelUtil.putFile(file, productInfoEnum);
    }

    private DbyFile putFile(File file, ProductInfoEnum productInfoEnum) throws IOException {
        return EasyExcelUtil.putFile(file, productInfoEnum);
    }

    /**
     * 获取总页数
     *
     * @param totalCount 总条数
     * @param exportBatchCount 单页导出数量
     * @return 总页数
     */
    public long getTotalPage(long totalCount, int exportBatchCount) {
        return totalCount % exportBatchCount != 0 ? totalCount / exportBatchCount + 1 : totalCount / exportBatchCount;
    }

    protected HorizontalCellStyleStrategy cellStyle() {
        // 头部策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 字体大小
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 12);
        headWriteCellStyle.setWriteFont(headWriteFont);

        // 内容策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 设置 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 设置垂直居中
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

    private Class<Model> modelClass() {
        Class<Model> clazz = null;
        Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        for (Type type : types) {
            Class<Model> modelClass = (Class<Model>) type;
            Class<? super Model> superclass = modelClass.getSuperclass();
            if (Objects.nonNull(superclass)) {
                clazz = modelClass;
                break;
            }
        }
        Assert.notNull(clazz, "undefined generics");
        return clazz;
    }

    /**
     * 转换表头
     * @param excelHeaders
     * @return
     */
    private List<List<String>> headers(List<ExcelHeader> excelHeaders) {
        List<List<String>> headers = new ArrayList<>();
        for (ExcelHeader header : excelHeaders) {
            List<String> head = new ArrayList<>();
            head.add(header.getHeadName());
            headers.add(head);
        }
        return headers;
    }

    /**
     * 要导出的字段
     * @param exportFields 表头集合
     * @param obj          数据对象
     * @return 集合
     */
    @SneakyThrows
    private <Model> List<List<Model>> dataList(List<ExcelHeader> exportFields, Model obj) {
        List<List<Model>> list = new ArrayList<>();
        List<Model> data = new ArrayList<>();
        //先根据反射获取实体类的class对象
        Class<?> objClass = obj.getClass();
        //设置实体类属性的集合
        Field[] fields = ReflectUtil.getFields(objClass);
        List<String> propList = exportFields.stream().map(ExcelHeader::getFieldName).collect(Collectors.toList());
        for (String prop : propList) {
            //循环实体类对象集合
            for (Field field : fields) {
                field.setAccessible(true);
                //判断实体类属性跟特定字段集合名是否一样
                if (field.getName().equals(prop)) {
                    Model object = (Model) field.get(obj);
                    //获取属性对应的值
                    data.add(object);
                }
            }
        }
        list.add(data);
        return list;
    }

    /**
     * 获取excel表头
     * @return
     */
    public List<ExcelHeader> getExcelHeader() {
        List<ExcelHeader> excelHeaders = Lists.newArrayList();
        Class<Model> modelClass = this.modelClass();
        Field[] fields = ReflectUtil.getFields(modelClass);
        for (Field field : fields) {
            field.setAccessible(true);
            excelHeaders.add(ExcelHeader.builder()
                    .headName(Objects.nonNull(field.getAnnotation(ExcelProperty.class)) ? Arrays.stream(field.getAnnotation(ExcelProperty.class).value()).findFirst().orElse(StringPool.EMPTY) : StringPool.EMPTY)
                    .fieldName(field.getName())
                    .build());
        }
        return excelHeaders;
    }

    @Override
    protected TaskAsyncParam beforeHandler(TaskAsyncParam asyncParam) {
        return asyncParam;
    }

    @Override
    protected final TaskAsyncResultTO handler(TaskAsyncParam asyncParam, Object beforeResult) {
        return super.handler(asyncParam, beforeResult);
    }
    @Override
    protected final Object afterHandler(TaskAsyncParam asyncParam, TaskAsyncResultTO taskAsyncResult) {
        return super.afterHandler(asyncParam, taskAsyncResult);
    }
}
